package org.casaca.checkbackup.core.task;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.casaca.checkbackup.core.checksum.Checksum;
import org.casaca.checkbackup.core.checksum.ChecksumGroup;
import org.casaca.checkbackup.core.checksum.ChecksumListener;
import org.casaca.checkbackup.core.data.Backup;
import org.casaca.checkbackup.core.data.File;
import org.casaca.checkbackup.core.data.Folder;
import org.casaca.checkbackup.core.data.SystemElement;
import org.casaca.checkbackup.core.exception.ChecksumResetException;

public class CheckBackup extends Task implements IMeasurableTask{

	private FileCollector fc;
	private Backup backup;
	private ChecksumGroup checksumGroup;
	private double partialProgress;
	private double totalProgress;
	private List<ChecksumListener> listeners;
	private BigInteger totalFoldersSize;
	private BigInteger partialFileReaded;
	private BigInteger totalFileReaded;
	private long beginMillis;
	
	public CheckBackup(Folder oFolder, Folder bFolder, ChecksumGroup checksumGroup){
		if(oFolder==null && bFolder==null && checksumGroup==null)
			throw new IllegalArgumentException("Backup and checksum group must not be null");
		
		this.listeners = new ArrayList<ChecksumListener>();
		
		this.partialFileReaded = this.totalFileReaded = BigInteger.ZERO;
		
		this.fc = new FileCollector();
		this.backup = new Backup(oFolder, bFolder);
		
		this.checksumGroup = checksumGroup;
		this.partialProgress = this.totalProgress = 0;
	}
	
	private void notifyAddedSlice(int sliceSize){
		for (ChecksumListener listener : this.listeners) {
			listener.addedSlice(sliceSize);
		}
	}
	
	private void notifyNewFile(File file){
		for (ChecksumListener listener : this.listeners) {
			listener.newFile(file);
		}
	}
	
	private void notifyNewFolder(Folder folder){
		for (ChecksumListener listener : this.listeners) {
			listener.newFolder(folder);
		}
	}
	
	private void notifyChecksumCalculated(String filepath, String checksumType, String checksum){
		for (ChecksumListener listener : this.listeners) {
			listener.checksumCalculated(filepath, checksumType, checksum);
		}
	}
	
	private void notifyCheckStarted(){
		for (ChecksumListener listener : this.listeners) {
			listener.checkStarted();
		}
	}
	
	private void notifyCheckFinished(){
		for (ChecksumListener listener : this.listeners) {
			listener.checkFinished();
		}
	}
	
	private void getChecksum(File file) throws IOException, ChecksumResetException{
		java.io.File f = new java.io.File(file.getPath());
		InputStream fis =  new FileInputStream(f);
		byte[] buffer = new byte[1024];
		int numRead;
		this.partialFileReaded = BigInteger.ZERO;
		
		do {
			numRead = fis.read(buffer);
			if (numRead > 0) {
				this.checksumGroup.addSlice(buffer, 0, numRead);
				this.partialFileReaded = this.partialFileReaded.add(BigInteger.valueOf(numRead));
				this.totalFileReaded = this.totalFileReaded.add(BigInteger.valueOf(numRead));
				this.partialProgress = (this.partialFileReaded.doubleValue()/file.getSize().doubleValue())*100;
				this.totalProgress = (this.totalFileReaded.doubleValue()/this.totalFoldersSize.doubleValue())*100;
				this.notifyAddedSlice(numRead);
			}
		} while (numRead != -1);
		fis.close();
		this.checksumGroup.complete();
		List<Checksum> checksums = this.checksumGroup.getChecksums();
		for (Checksum checksum : checksums) {
			file.getChecksums().put(checksum.getChecksumType(), checksum.getChecksum());
			this.notifyChecksumCalculated(file.getPath(), checksum.getChecksumType(), checksum.getChecksum());
		}
		this.checksumGroup.reset();
	}
	
	private void check(Folder folder) throws IOException, ChecksumResetException{
		List<SystemElement> children = folder.getChildren();
		for (SystemElement systemElement : children) {
			if(systemElement instanceof File){
				this.notifyNewFile((File)systemElement);
				this.getChecksum((File)systemElement);
			}
			else if(systemElement instanceof Folder){
				this.notifyNewFolder((Folder)systemElement);
				this.check((Folder)systemElement);
			}
		}
	}
	
	@Override
	public void run() {
		this.beginMillis = System.currentTimeMillis();
		this.notifyCheckStarted();
		this.fc.setBackup(this.backup);
		TaskManager.executeWithoutThread(this.fc);
		this.backup = this.fc.getBackup();
		//Force to calculate the folder sizes
		this.backup.getOriginalFolder().getSize();
		this.backup.getBackupFolder().getSize();
		this.totalFoldersSize = this.backup.getOriginalFolder().getSize().add(this.backup.getBackupFolder().getSize());
		
		try {
			this.check(this.backup.getOriginalFolder());
			this.check(this.backup.getBackupFolder());
			this.notifyCheckFinished();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ChecksumResetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public FileCollector getFileCollector(){
		return this.fc;
	}
	
	public BigInteger getTotalDataProcessed(){
		return this.totalFileReaded;
	}

	@Override
	public double getPartialProgress() {
		return this.partialProgress;
	}
	
	@Override
	public double getTotalProgress() {
		return this.totalProgress;
	}
	
	@Override
	public long getEstimatedMillis() {
		return (this.totalFoldersSize.subtract(this.totalFileReaded).longValue()*this.getElapsedMillis())/this.totalFileReaded.longValue();
	}

	@Override
	public long getElapsedMillis() {
		return System.currentTimeMillis()-this.beginMillis;
	}
	
	public Backup getBackup(){
		return this.backup;
	}
	
	public void addChecksumListener(ChecksumListener listener){
		this.listeners.add(listener);
	}
	
	public void removeChecksumListener(ChecksumListener listener){
		this.listeners.remove(listener);
	}
}